package com.soup.lab.mybatis.generator.plugin;

import org.mybatis.generator.api.FullyQualifiedTable;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.OutputUtilities;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.Parameter;
import org.mybatis.generator.api.dom.xml.Attribute;
import org.mybatis.generator.api.dom.xml.Document;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.codegen.mybatis3.ListUtilities;
import org.mybatis.generator.codegen.mybatis3.MyBatis3FormattingUtilities;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 批量新增 mapper方法插件
 * int insertBatch(@Param("records") List<Test> records);
 *
 * @author soup
 * @date 2021-04-30
 */
public class InsertBatchPlugin extends PluginAdapter {

    private Map<FullyQualifiedTable, List<XmlElement>> elementsToAdd = new HashMap<>();

    @Override
    public boolean validate(List<String> warnings) {
        return true;
    }

    @Override
    public boolean clientInsertMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        if (introspectedTable.getTargetRuntime() == IntrospectedTable.TargetRuntime.MYBATIS3) {
            copyAndAddMethod(method, interfaze);
        }
        return true;
    }

    @Override
    public boolean sqlMapInsertElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        if (introspectedTable.getTargetRuntime() == IntrospectedTable.TargetRuntime.MYBATIS3) {
            copyAndSaveElement(element, introspectedTable);
        }
        return true;
    }

    private void copyAndAddMethod(Method method, Interface interfaze) {
        Method newMethod = new Method(method);
        newMethod.setName(method.getName() + "Batch"); //$NON-NLS-1$
        List<Parameter> parameters = newMethod.getParameters();
        Parameter parameter = parameters.get(0);
        FullyQualifiedJavaType javaType = FullyQualifiedJavaType.getNewListInstance();
        javaType.addTypeArgument(parameter.getType());
        parameters.clear();
        newMethod.addParameter(new Parameter(javaType, "records", "@Param(\"records\")")); //$NON-NLS-1$ //$NON-NLS-2$
        interfaze.addMethod(newMethod);
    }


    private void copyAndSaveElement(XmlElement element, IntrospectedTable introspectedTable) {
        XmlElement answer = new XmlElement("insert"); //$NON-NLS-1$

        answer.addAttribute(new Attribute(
                "id", introspectedTable.getInsertStatementId() + "Batch")); //$NON-NLS-1$ //$NON-NLS-2$

        answer.addAttribute(new Attribute("parameterType", //$NON-NLS-1$
                introspectedTable.getRules().calculateAllFieldsClass().getFullyQualifiedName()));

        StringBuilder insertClause = new StringBuilder();

        insertClause.append("insert into "); //$NON-NLS-1$
        insertClause.append(introspectedTable
                .getFullyQualifiedTableNameAtRuntime());
        insertClause.append(" ("); //$NON-NLS-1$

        StringBuilder valuesClause = new StringBuilder();
        valuesClause.append("("); //$NON-NLS-1$

        List<String> valuesClauses = new ArrayList<>();
        List<IntrospectedColumn> columns =
                ListUtilities.removeIdentityAndGeneratedAlwaysColumns(introspectedTable.getAllColumns());
        for (int i = 0; i < columns.size(); i++) {
            IntrospectedColumn introspectedColumn = columns.get(i);

            insertClause.append(MyBatis3FormattingUtilities
                    .getEscapedColumnName(introspectedColumn));
            valuesClause.append(MyBatis3FormattingUtilities
                    .getParameterClause(introspectedColumn, "record.")); //$NON-NLS-1$
            if (i + 1 < columns.size()) {
                insertClause.append(", "); //$NON-NLS-1$
                valuesClause.append(", "); //$NON-NLS-1$
            }

            if (valuesClause.length() > 120) {
                answer.addElement(new TextElement(insertClause.toString()));
                insertClause.setLength(0);
                OutputUtilities.xmlIndent(insertClause, 1);

                valuesClauses.add(valuesClause.toString());
                valuesClause.setLength(0);
                OutputUtilities.xmlIndent(valuesClause, 1);
            }
        }

        insertClause.append(")  values ");  //$NON-NLS-1$
        answer.addElement(new TextElement(insertClause.toString()));

        XmlElement foreach = new XmlElement("foreach"); //$NON-NLS-1$
        foreach.addAttribute(new Attribute("collection", "records"));   //$NON-NLS-1$ //$NON-NLS-2$
        foreach.addAttribute(new Attribute("item", "record"));  //$NON-NLS-1$ //$NON-NLS-2$
        foreach.addAttribute(new Attribute("separator", ","));  //$NON-NLS-1$ //$NON-NLS-2$

        valuesClause.append(')'); //$NON-NLS-1$
        valuesClauses.add(valuesClause.toString());

        for (String clause : valuesClauses) {
            foreach.addElement(new TextElement(clause));
        }
        answer.addElement(foreach);
        elementsToAdd.computeIfAbsent(introspectedTable.getFullyQualifiedTable(),
                k -> new ArrayList<>()).add(answer);
    }

    @Override
    public boolean sqlMapDocumentGenerated(Document document,
                                           IntrospectedTable introspectedTable) {
        List<XmlElement> elements = elementsToAdd.get(introspectedTable.getFullyQualifiedTable());
        if (elements != null) {
            for (XmlElement element : elements) {
                document.getRootElement().addElement(element);
            }
        }
        return true;
    }
}
